package cn.wgx.springsecuritystudy.config.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.security.SpringSessionBackedSessionRegistry;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    AccessDecisionManager myAccessDecisionManager;

    @Autowired
    FilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;

    @Autowired
    private FindByIndexNameSessionRepository sessionRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    @Qualifier("userDetailService")
//    @Qualifier("jdbcUserDetailsManager")
    private UserDetailsService userDetailService;

    /**
     * 配置登陆用户来源
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailService)
                .passwordEncoder(passwordEncoder);
        //内存用户
//        auth.inMemoryAuthentication()
////                .passwordEncoder(NoOpPasswordEncoder.getInstance())
//                .passwordEncoder(passwordEncoder)
//                .withUser("admin").roles("admin")
//                .password(passwordEncoder.encode("123123wwww"));
//                .withUser("user1").password("123123").roles("admin");
//        super.configure(auth);
    }



    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                // 关闭csrf防护
                .csrf().disable()
                .exceptionHandling()
                //配置未登录返回信息,json模式
                .authenticationEntryPoint((request,resp,a)->{
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write("尚未登录，请先登录");
                    out.flush();
                    out.close();
                })
                .and()
                .headers().frameOptions().disable()
                .and()
                //默认登录地址界面
//                .formLogin()
//                .permitAll()
//                .and()
                .rememberMe()
                .and()
                .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>(){
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(myAccessDecisionManager);
                        object.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
                        return object;
                    }
                })
                //权限通配符
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")
                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated()
//                .and()
//                //同一用户最大在线数
//                .sessionManagement()
//                .maximumSessions(1).sessionRegistry(sessionRegistry())
//                //不允许第二个人登录
//                .maxSessionsPreventsLogin(true)
//                .and()
                .and()
//替换原本的用户名密码验证过滤器,自定义过滤器添加验证码等功能
                .addFilterAt(filter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
                    HttpServletResponse resp = event.getResponse();
                    resp.setContentType("application/json;charset=utf-8");
                    resp.setStatus(401);
                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString("您已在另一台设备登录，本次登录已下线!"));
                    out.flush();
                    out.close();
                }), ConcurrentSessionFilter.class)

//                .exceptionHandling((e)-> e.accessDeniedHandler((request, response, e1)->{
//            PrintWriter writer = response.getWriter();
//            writer.write(e1.getMessage());
//            writer.close();
//        }))
        ;
    }


    public MyUsernamePasswordAuthenticationFilter filter() throws Exception {
        MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter =
                new MyUsernamePasswordAuthenticationFilter(authenticationManagerBean(),sessionRegistry());
        myUsernamePasswordAuthenticationFilter
                //登录成功返回json,前后分离页面使用
                .setAuthenticationSuccessHandler((request, response, authentication) -> {
                    Object principal = authentication.getPrincipal();
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.print(new ObjectMapper().writeValueAsString(principal));
                    writer.close();
                });
        myUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler((request, response, exception) -> {
            String eMsg = "";
            if (exception instanceof LockedException) {
                eMsg = "账户被锁定!";
            } else if (exception instanceof UsernameNotFoundException) {
                eMsg = "账户找不到!";
            } else if (exception instanceof BadCredentialsException) {
                eMsg = "密码错误!";
            } else if (exception instanceof BadCredentialsException) {
                eMsg = "用户名或者密码输入错误，请重新输入!";
            } else if (exception instanceof AccountExpiredException) {
                eMsg = "账户过期，请联系管理员!";
            } else if (exception instanceof DisabledException) {
                eMsg = "账户被禁用，请联系管理员!";
            } else {
                eMsg = exception.getMessage();
            }
            Map map = new HashMap();
            map.put("eMsg", eMsg);
            ObjectMapper mapper = new ObjectMapper();
            mapper.findAndRegisterModules();
            String s = mapper.writeValueAsString(map);
            response.setContentType("application/json;charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.close();
        });
        myUsernamePasswordAuthenticationFilter
                .setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
        ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
        //同一账户同时在线人数
        sessionStrategy.setMaximumSessions(1);
        myUsernamePasswordAuthenticationFilter.setSessionAuthenticationStrategy(sessionStrategy);
        return myUsernamePasswordAuthenticationFilter;
    }

    /**
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) {
        //不检测权限的地址
        web.ignoring()
           .antMatchers("/js/**", "/css/**", "/img/**", "/images/**")
        ;
    }
    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry(sessionRepository);
    }


    /**
     * 配置角色继承关系 admin包含user权限
     *
     * @return
     */
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_admin > ROLE_user");
        return roleHierarchy;
    }

    /**
     * 密码加密
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
